package org.jbrisk.tests;

import junit.framework.AssertionFailedError;

import org.jbrisk.exceptions.EmptyStringArgumentException;
import org.jbrisk.exceptions.NullArgumentException;
import org.junit.Assert;

/**
 * Provides methods to assert that exceptions are thrown when executing some
 * code.
 * 
 * @author Rafael
 * 
 */
public class ExceptionAssert {

	/**
	 * Asserts that a {@link EmptyStringArgumentException} exception is thrown
	 * when executing the supplied code.
	 * 
	 * @param argumentName
	 *            Argument name.
	 * @param codeToRun
	 *            Code to execute.
	 */
	public static void assertEmptyStringArgumentException(String argumentName, IExceptionAssertRunner codeToRun) {

		assertException("Argument \"" + argumentName + "\" cannot be an empty (zero-length) String!", EmptyStringArgumentException.class, codeToRun);
	}

	/**
	 * Asserts that a {@link NullArgumentException} exception is thrown when
	 * executing the supplied code.
	 * 
	 * @param argumentName
	 *            Argument that its null.
	 * @param codeToRun
	 *            Code to execute.
	 */
	public static void assertNullArgumentException(String argumentName, IExceptionAssertRunner... codeToRun) {

		assertException("Argument \"" + argumentName + "\" cannot be null!", NullArgumentException.class, codeToRun);
	}

	/**
	 * Asserts that an exception its indeed thrown when executing the supplied
	 * code.
	 * 
	 * @param <T>
	 *            Exception type.
	 * @param exceptionType
	 *            Exception type.
	 * @param codeToRun
	 *            Code to run.
	 */
	public static <T extends Throwable> void assertException(Class<T> exceptionType, IExceptionAssertRunner codeToRun) {

		assertException(null, false, exceptionType, codeToRun);
	}

	/**
	 * Asserts that an exception its indeed thrown when executing the supplied
	 * code and that its message its the same as the supplied one.
	 * 
	 * @param exceptionMessage
	 *            Exception message.
	 * @param exceptionType
	 *            Exception type.
	 * @param codeToRun
	 *            Code to run.
	 * 
	 * @param <T>
	 *            Exception type.
	 */
	public static <T extends Throwable> void assertException(String exceptionMessage, Class<T> exceptionType, IExceptionAssertRunner codeToRun) {

		assertException(exceptionMessage, exceptionType, new IExceptionAssertRunner[] { codeToRun });
	}

	/**
	 * Asserts that an exception its indeed thrown when executing the supplied
	 * list {@link IExceptionAssertRunner} instances and that its message its
	 * the same as the supplied one.
	 * 
	 * @param exceptionMessage
	 *            Exception message.
	 * @param exType
	 *            Exception type.
	 * @param runners
	 *            List of {@link IExceptionAssertRunner} instances to execute.
	 * 
	 * @param <T>
	 *            Exception type.
	 */
	public static <T extends Throwable> void assertException(String exceptionMessage, Class<T> exType, IExceptionAssertRunner... runners) {

		assertException(exceptionMessage, true, exType, runners);
	}

	/**
	 * Asserts that an exception its indeed thrown when executing the supplied
	 * list {@link IExceptionAssertRunner} instances and that its message its
	 * the same as the supplied one.
	 * 
	 * @param exceptionMessage
	 *            Exception message.
	 * @param exType
	 *            Exception type.
	 * @param runners
	 *            List of {@link IExceptionAssertRunner} instances to execute.
	 * 
	 * @param <T>
	 *            Exception type.
	 */
	public static <T extends Throwable> void assertException(String exceptionMessage, boolean compareMessage, Class<T> exType,
			IExceptionAssertRunner... runners) {

		if (exType == null)
			throw new NullArgumentException("exType");

		if (runners == null)
			throw new NullArgumentException("runners");

		for (int runnerIdx = 0; runnerIdx < runners.length; runnerIdx++) {

			IExceptionAssertRunner codeToRun = runners[runnerIdx];

			Throwable thrownEx = null;

			try {

				codeToRun.execute();

			} catch (Throwable ex) {

				thrownEx = ex;
			}

			// Checks if an exception was thrown
			if (thrownEx == null)
				throw new AssertionFailedError("The " + IExceptionAssertRunner.class.getSimpleName() + " instance supplied at index \"" + runnerIdx
						+ "\" should have throwed an exception from class \"" + exType.getName() + "\"!");

			// Checks if the thrown exception type matches the excepted
			if (thrownEx != null && !exType.isAssignableFrom(thrownEx.getClass())) {

				thrownEx.printStackTrace();

				throw new AssertionFailedError(String.format("An exception from class %s was expected but it was thrown an exception from type %s instead!",
						exType.getName(), thrownEx.getClass().getName()));
			}

			if (compareMessage) {
				
				// If a message was supplied, checks if it matches
				Assert.assertEquals("Wrong exception message at the " + IExceptionAssertRunner.class.getSimpleName() + " instance supplied at index \""
						+ runnerIdx + "\"!", exceptionMessage, thrownEx.getMessage());
			}
		}
	}
}
